package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.discount.Discount;
import com.tianji.promotion.discount.DiscountStrategy;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCouponDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.util.CodeUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;


/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author liuchun
 * @since 2024-10-25
 */
@Service
@Slf4j
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    @Autowired
    private CouponMapper couponMapper;

    @Autowired
    private IExchangeCodeService exchangeCodeService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitMqHelper rabbitMqHelper;

    @Autowired
    private ICouponScopeService couponScopeService;

    //todo 通用分布式锁组件的封装
    //领取发放中的优惠券
    @Override
    public void receiveCoupon(Long id) {
//        Long userId = UserContext.getUser();
//        //相关校验
//        if (id == null) {
//            throw new BizIllegalException("优惠券id不能为空");
//        }
//        Coupon coupon = couponMapper.selectById(id);
//        if (coupon == null) {
//            throw new BizIllegalException("优惠券不存在");
//        }
//        if (coupon.getStatus() != CouponStatus.ISSUING) {
//            throw new BizIllegalException("优惠券不在发放中状态");
//        }
//        //判断库存是否充足
//        if (coupon.getIssueNum() >= coupon.getTotalNum()) {
//            throw new BizIllegalException("优惠券库存不足");
//        }
//        //是否超过领取数量
//        /*Integer count = lambdaQuery().eq(UserCoupon::getCouponId, id).eq(UserCoupon::getUserId, userId).count();
//        if(count != null && count >= coupon.getUserLimit()){
//            throw new BizIllegalException("超过领取数量限制");
//        }
//        //优惠券领取已经发放数量+1
//        couponMapper.incrIssueNum(id);
//        //生成用户券
//        saveUserCoupon(userId, coupon);*/
//        /*synchronized (userId.toString().intern()){
//            //获取代理对象
//            IUserCouponService userCouponServiceProxy = (IUserCouponService) AopContext.currentProxy();
////            userCouponService.checkAndCreateUserCoupon(userId,coupon,null);
//            userCouponServiceProxy.checkAndCreateUserCoupon(userId,coupon,null);//调用代理对象的方法，方法是有事务处理的
//        }*/
//        //分布式锁
//        String key = "lock:coupon:uid:" + userId;
//        //获得锁对象
//        RLock lock = redissonClient.getLock(key);
//        //不设置时间，设置时间会导致看门狗失效
//        boolean success = lock.tryLock();
//        if (success) {
//            try{
//                //获取代理对象
//                IUserCouponService userCouponServiceProxy = (IUserCouponService) AopContext.currentProxy();
//                userCouponServiceProxy.checkAndCreateUserCoupon(userId,coupon,null);//调用代理对象的方法，方法是有事务处理的
//            }finally {
//                lock.unlock();
//            }
//        }else {
//            throw new BizIllegalException("操作太频繁了");
//        }
        /*if (id ==  null){
            throw new BizIllegalException("优惠券id不能为空");
        }
        Coupon coupon = queryCouponByCache(id);
        log.info("Redis缓存中的优惠券信息:{}",coupon);
        if(coupon == null){
            throw new BizIllegalException("优惠券信息不存在");
        }
        //判断库存是否充足
        if(coupon.getTotalNum() <= 0){
            throw new BizIllegalException("优惠券已领完");
        }

        //锁优惠券id
        String lockKey = "lock:coupon:uid:" + id;
        //获得锁对象
        RLock lock = redissonClient.getLock(lockKey);
        //不设置时间，设置时间会导致看门狗失效
        boolean success = lock.tryLock();
        if(success){
            try {
                //是否超过领取数量
                String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + id;
                Long count = redisTemplate.opsForHash().increment(key, UserContext.getUser().toString(), 1);
                if(count > coupon.getUserLimit()){
                    throw new BizIllegalException("超过领取数量限制");
                }
                //扣减缓存中的优惠券库存
                redisTemplate.opsForHash().increment(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id, "totalNum", -1);

                //发送MQ异步消息
                UserCouponDTO dto = new UserCouponDTO();
                dto.setUserId(UserContext.getUser());
                dto.setCouponId(id);
                rabbitMqHelper.send(MqConstants.Exchange.RECEIVE_COUPON_EXCHANGE,MqConstants.Key.receive_coupon,dto);
            } finally {
                    lock.unlock();
            }
        }else {
            throw new BizIllegalException("操作频繁");
        }

    }
    */

        String lockKey = "lock:coupon:id:" + id; // 针对每个优惠券 ID 使用全局锁
        RLock lock = redissonClient.getLock(lockKey);
        boolean success = lock.tryLock();
        if (success) {
            try {
                if (id == null) {
                    throw new BizIllegalException("优惠券id不能为空");
                }

                // 获得锁后重新检查库存
                Coupon coupon = queryCouponByCache(id);
                if (coupon == null) {
                    throw new BizIllegalException("优惠券信息不存在");
                }
                if (coupon.getTotalNum() <= 0) {
                    throw new BizIllegalException("优惠券已领完");
                }

                String userCouponKey = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + id;
                Long count = redisTemplate.opsForHash().increment(userCouponKey, UserContext.getUser().toString(), 1);
                if (count > coupon.getUserLimit()) {
                    throw new BizIllegalException("超过领取数量限制");
                }

                // 使用 Lua 脚本原子性扣减库存
                String script = "if (redis.call('HGET', KEYS[1], 'totalNum') > '0') then " +
                        "return redis.call('HINCRBY', KEYS[1], 'totalNum', -1); " +
                        "else return -1; end";
                List<String> keys = Collections.singletonList(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id);
                Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), keys);

                if (result == null || result == -1) {
                    throw new BizIllegalException("优惠券已领完");
                }

                // 发送 MQ 异步消息
                UserCouponDTO dto = new UserCouponDTO();
                dto.setUserId(UserContext.getUser());
                dto.setCouponId(id);
                rabbitMqHelper.send(MqConstants.Exchange.RECEIVE_COUPON_EXCHANGE, MqConstants.Key.receive_coupon, dto);

            } finally {
                lock.unlock();
            }
        } else {
            throw new BizIllegalException("操作频繁");
        }

    }
    //从缓存中去查询优惠券相关信息
    private Coupon queryCouponByCache(Long couponId) {
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
        if(map.isEmpty()){
            return null;
        }
        return BeanUtils.mapToBean(map, Coupon.class, false, CopyOptions.create());
    }

    //保存用户券
    private void saveUserCoupon(Long userId, Coupon coupon) {
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(userId);
        userCoupon.setCouponId(coupon.getId());
        userCoupon.setStatus(UserCouponStatus.UNUSED);
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if(termBeginTime == null && termEndTime == null){
            LocalDateTime now = LocalDateTime.now();
            termBeginTime = now;
            termEndTime = now.plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(termBeginTime);
        userCoupon.setTermEndTime(termEndTime);

        this.save(userCoupon);
    }

    //兑换码兑换优惠券
    @Override
    public void exchangeCoupon(String code) {
        //校验
        if (code == null || code.isEmpty()){
            throw new BadRequestException("非法参数");
        }
        //校验并解析兑换码
        long serialNum = CodeUtil.parseCode(code);
        //判断是否已经兑换
        boolean result = exchangeCodeService.checkIsExchanged(serialNum, true);
        if (result){
            throw new BizIllegalException("兑换码已使用");
        }
        try {
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            if (exchangeCode == null){
                throw new BizIllegalException("兑换码不存在");
            }
            //校验是否过期
            if(exchangeCode.getExpiredTime().isBefore(LocalDateTime.now())){
                throw new BizIllegalException("兑换码已过期");
            }
            Long userId = UserContext.getUser();
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            if (coupon == null) {
                throw new BizIllegalException("优惠券不存在");
            }
//            synchronized (userId.toString().intern()){
//                //获取代理对象
//                IUserCouponService userCouponServiceProxy = (IUserCouponService) AopContext.currentProxy();
//                userCouponServiceProxy.checkAndCreateUserCoupon(userId,coupon,serialNum);
//            }
            String key = "lock:coupon:uid"+userId;
            RLock lock = redissonClient.getLock(key);
            boolean success = lock.tryLock();
            if (success) {
                try {
                    //获取代理对象
                    IUserCouponService userCouponServiceProxy = (IUserCouponService) AopContext.currentProxy();
                    userCouponServiceProxy.checkAndCreateUserCoupon(userId,coupon,serialNum);
            }finally {
                lock.unlock();
                }
            }else {
                throw new BizIllegalException("操作太频繁");
            }
        } catch (BizIllegalException e) {
            //redis中状态回滚
            exchangeCodeService.checkIsExchanged(serialNum, false);
            throw new RuntimeException(e);
        }
    }

    //校验并生成用户券
    @Transactional
    public void checkAndCreateUserCoupon(Long userId, Coupon coupon,Long serialNum) {
        //是否超过领取数量
        Integer count = lambdaQuery().eq(UserCoupon::getCouponId, coupon.getId()).eq(UserCoupon::getUserId, userId).count();
        if(count != null && count >= coupon.getUserLimit()){
            throw new BizIllegalException("超过用户领取数量限制");
        }
        //优惠券领取已经发放数量+1
        int res = couponMapper.incrIssueNum(coupon.getId());
        if (res == 0){
            throw new BizIllegalException("优惠券库存不足");
        }
        //保存用户券
        saveUserCoupon(userId, coupon);
        //更新兑换码状态
        if (serialNum != null){
            exchangeCodeService.lambdaUpdate().set(ExchangeCode::getUserId, userId)
                    .eq(ExchangeCode::getId, serialNum)
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED).update();
        }
        //throw new BizIllegalException("故意报错");
    }

    @Override
    @Transactional
    public void checkAndCreateUserCoupon(UserCouponDTO msg) {
//        String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + msg.getCouponId();
//        Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
//        if(map.containsKey(msg.getUserId())){
//            throw new BizIllegalException("用户已经领取过该优惠券");
//        }
        Coupon coupon = couponMapper.selectById(msg.getCouponId());
        if (coupon == null){
            return;//这里是return，不要抛异常，否则MQ会进行重试。
        }
        //优惠券已领取数量+1
        couponMapper.incrIssueNum(msg.getCouponId());
        //生成用户券
        saveUserCoupon(msg.getUserId(), coupon);
    }

    //查询可用的优惠券
    @Override
    public List<CouponDiscountDTO> queryDiscountCoupons(List<OrderCourseDTO> courses) {
        //查询当前用户可用的优惠券
        List<Coupon> couponList = getBaseMapper().queryMyCoupons(UserContext.getUser());
        if (couponList.isEmpty()){
            return CollUtils.emptyList();
        }
        //初筛
        int totalPrice = courses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        List<Coupon> availableCoupons = couponList.stream().filter(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalPrice, coupon)).collect(Collectors.toList());
        if (CollUtils.isEmpty(availableCoupons)){
            return CollUtils.emptyList();
        }
        //细筛（需要考虑优惠券使用范围）排列组合
        Map<Coupon,List<OrderCourseDTO>> map= findAvailableCoupons(availableCoupons,courses);
        if (CollUtils.isEmpty(map)){
            return CollUtils.emptyList();
        }
        //细筛选后，真正可用的优惠券集合
        couponList = new ArrayList<>(map.keySet());
        //计算每一种组合的优惠明细
        //使用多线程改造，并行 计算每一种组合的优惠明细
     }
    
    //细筛
    private Map<Coupon, List<OrderCourseDTO>> findAvailableCoupons(List<Coupon> availableCoupons, List<OrderCourseDTO> courses) {
        Map<Coupon,List<OrderCourseDTO>> map=new HashMap<>();
        //循环遍历初筛后的优惠券
        for (Coupon coupon : availableCoupons) {
            List<OrderCourseDTO> availableCourses = courses;
            //判断优惠券是否设置了限定范围
            if(coupon.getSpecific()){
                //查询限定范围
                List<CouponScope> scopeList = couponScopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();
                List<Long> scopeIds = scopeList.stream().map(CouponScope::getBizId).collect(Collectors.toList());

                //判断课程是否在限定范围内
                availableCourses = courses.stream().filter(orderCourseDTO->scopeIds
                        .contains(orderCourseDTO.getCateId()))
                        .collect(Collectors.toList());
            }
            if (CollUtils.isEmpty(availableCoupons)){
                continue;//没有可用的课程，跳过
            }
            //计算优惠券 可用课程的总金额
            int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();

            //判断优惠券是否满足使用条件
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if(discount.canUse(totalAmount,coupon)){
                map.put(coupon, availableCourses);
            }
        }
            return map;
    }
}
